use std::collections::VecDeque;
use std::marker::PhantomData;
use std::sync::Arc;

use winit::dpi::PhysicalPosition;
use winit::event::{KeyEvent, MouseScrollDelta};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::{
    dpi::PhysicalSize,
    event::{ElementState, Event, WindowEvent},
    window::WindowBuilder,
};

use graphics::{Graphics, Point, Size2D, Vector};

use crate::contexts::{EventCtx, LayoutCtx, LifeCycleCtx};
use crate::event::{KeyboardInfo, LifeCycle, ModifiersState, MouseInfo, ViewContext};
use crate::id::Id;
use crate::view::Cx;
use crate::widget::{Pod, PodFlags, WidgetState};
use crate::{
    contexts::{CxState, PaintCtx},
    event,
    view::View,
};

pub struct App<T, V: View<T, A>, A = ()>
where
    V::State: 'static,
    V::Element: 'static,
    T: 'static,
{
    view: Box<V>,
    root_pod: Option<Pod>,
    root_state: WidgetState,
    app_logic: fn(&mut T) -> V,
    state: Option<V::State>,
    app_state: T,
    id: Option<Id>,
    events: VecDeque<event::Event>,
    mouse_pos: Option<(f32, f32)>,
    modifiers: ModifiersState,
    _phantom_data: PhantomData<A>,
}

impl<T, V: View<T, A> + 'static, A> App<T, V, A> {
    pub fn new(app_logic: fn(&mut T) -> V, mut app_state: T) -> Self {
        let root_view = Box::new(app_logic(&mut app_state));
        Self {
            view: root_view,
            root_pod: None,
            root_state: Default::default(),
            app_logic,
            state: None,
            app_state,
            id: None,
            events: VecDeque::with_capacity(10),
            mouse_pos: None,
            modifiers: Default::default(),
            _phantom_data: PhantomData::default(),
        }
    }

    pub fn run(mut self) {
        let event_loop = EventLoop::new().unwrap();
        let window = WindowBuilder::new()
            .with_inner_size(PhysicalSize::new(512.0, 512.0))
            .build(&event_loop)
            .unwrap();
        let window = Arc::new(window);
        // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
        // dispatched any events. This is ideal for games and similar applications.
        event_loop.set_control_flow(ControlFlow::Poll);

        // ControlFlow::Wait pauses the event loop if no events are available to process.
        // This is ideal for non-game applications that only update in response to user
        // input, and uses significantly less power/CPU time than ControlFlow::Poll.
        event_loop.set_control_flow(ControlFlow::Wait);

        // State::new uses async code, so we're going to wait for it to finish
        let mut graphics = pollster::block_on(Graphics::new(window.clone()));
        event_loop
            .run(move |event, elwt| {
                match event {
                    Event::WindowEvent {
                        ref event,
                        window_id,
                    } if window_id == window.id() => {
                        match event {
                            WindowEvent::CloseRequested => elwt.exit(),
                            WindowEvent::Resized(physical_size) => {
                                graphics.resize(&Size2D::new(
                                    physical_size.width,
                                    physical_size.height,
                                ));
                            }
                            // WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer } => {
                            //     new_inner_size is &mut so w have to dereference it twice
                            // graphics.resize(*new_inner_size);
                            // }
                            WindowEvent::KeyboardInput {
                                event:
                                    KeyEvent {
                                        physical_key: _,
                                        text,
                                        location: _,
                                        state,
                                        logical_key,
                                        repeat: _,
                                        ..
                                    },
                                ..
                            } => {
                                let info = KeyboardInfo {
                                    logical_key: logical_key.clone().into(),
                                    text: text.clone(),
                                    modifiers: self.modifiers,
                                };

                                let event = match state {
                                    ElementState::Pressed => event::Event::KeyPressed(info),
                                    ElementState::Released => event::Event::KeyReleased(info),
                                };
                                self.events.push_back(event)
                            }
                            WindowEvent::MouseInput {
                                device_id: _,
                                state,
                                button,
                                ..
                            } => {
                                let mouse_info = MouseInfo {
                                    pos: self
                                        .mouse_pos
                                        .map(|pos| Point::new(pos.0, pos.1))
                                        .unwrap(),
                                    window_pos: self
                                        .mouse_pos
                                        .map(|pos| Point::new(pos.0, pos.1))
                                        .unwrap(),
                                    button: (*button).into(),
                                    wheel_delta: Default::default(),
                                };
                                match state {
                                    ElementState::Pressed => {
                                        let event = event::Event::MousePressed(mouse_info);
                                        self.events.push_back(event)
                                    }
                                    ElementState::Released => {
                                        let event = event::Event::MouseReleased(mouse_info);
                                        self.events.push_back(event)
                                    }
                                }
                            }
                            WindowEvent::ModifiersChanged(modifiers) => {
                                self.modifiers =
                                    ModifiersState::from_bits(modifiers.state().bits()).unwrap();
                            }
                            WindowEvent::MouseWheel {
                                device_id: _,
                                delta,
                                phase: _,
                                ..
                            } => match delta {
                                MouseScrollDelta::LineDelta(x, y) => {
                                    let mouse_info = MouseInfo {
                                        pos: self
                                            .mouse_pos
                                            .map(|pos| Point::new(pos.0, pos.1))
                                            .unwrap(),
                                        window_pos: self
                                            .mouse_pos
                                            .map(|pos| Point::new(pos.0, pos.1))
                                            .unwrap(),
                                        button: event::MouseButton::None,
                                        wheel_delta: Vector::new(*x, *y),
                                    };
                                    let event = event::Event::MouseWheel(mouse_info);
                                    self.events.push_back(event);
                                }
                                MouseScrollDelta::PixelDelta(PhysicalPosition { x, y }) => {
                                    let mouse_info = MouseInfo {
                                        pos: self
                                            .mouse_pos
                                            .map(|pos| Point::new(pos.0, pos.1))
                                            .unwrap(),
                                        window_pos: self
                                            .mouse_pos
                                            .map(|pos| Point::new(pos.0, pos.1))
                                            .unwrap(),
                                        button: event::MouseButton::None,
                                        wheel_delta: Vector::new(*x as f32, *y as f32),
                                    };
                                    let event = event::Event::MouseWheel(mouse_info);
                                    self.events.push_back(event);
                                }
                            },
                            WindowEvent::CursorMoved {
                                device_id: _,
                                position,
                                ..
                            } => {
                                self.mouse_pos = Some((position.x as f32, position.y as f32));
                                let mouse_info = MouseInfo {
                                    pos: self
                                        .mouse_pos
                                        .map(|pos| Point::new(pos.0, pos.1))
                                        .unwrap(),
                                    window_pos: self
                                        .mouse_pos
                                        .map(|pos| Point::new(pos.0, pos.1))
                                        .unwrap(),
                                    button: event::MouseButton::None,
                                    wheel_delta: Default::default(),
                                };
                                let event = event::Event::MouseMove(mouse_info);
                                self.events.push_back(event)
                            }
                            WindowEvent::CursorLeft { .. } => {
                                self.mouse_pos = None;
                                let event = event::Event::MouseLeft;
                                self.events.push_back(event)
                            }
                            WindowEvent::RedrawRequested => {
                                let Self {
                                    app_state,
                                    app_logic,
                                    root_pod,
                                    events,
                                    root_state,
                                    id,
                                    state,
                                    view,
                                    mouse_pos,
                                    ..
                                } = &mut self;
                                let new_view: V = app_logic(app_state);

                                let mut cx_state = CxState {
                                    window: window.clone(),
                                    messages: VecDeque::with_capacity(10),
                                };
                                if let Some(root_pod) = root_pod {
                                    let mut cx = Cx {
                                        id_path: vec![id.unwrap()],
                                    };
                                    while let Some(event) = events.pop_front() {
                                        let mut event_cx = EventCtx {
                                            cx_state: &mut cx_state,
                                            is_handled: false,
                                            widget_state: root_state,
                                        };
                                        root_pod.event(&mut event_cx, &event)
                                    }

                                    // Блок кода ниже нужен чтобы has_active обнулялся на всех Pod на которых он больше не нужен
                                    {
                                        let mut event_cx = EventCtx {
                                            cx_state: &mut cx_state,
                                            is_handled: false,
                                            widget_state: root_state,
                                        };
                                        root_pod.event(&mut event_cx, &event::Event::None);
                                    }

                                    while let Some(message) = cx_state.messages.pop_front() {
                                        new_view.message(
                                            &message.id_path[1..],
                                            state.as_mut().unwrap(),
                                            message.body,
                                            app_state,
                                        );
                                    }

                                    let element: &mut <V as View<T, A>>::Element =
                                        root_pod.widget.as_any_mut().downcast_mut().unwrap();
                                    let rebuild_flags = new_view.rebuild(
                                        view,
                                        &mut cx,
                                        id.as_mut().unwrap(),
                                        state.as_mut().unwrap(),
                                        element,
                                    );
                                    root_pod.mark(rebuild_flags);

                                    if root_pod.state.flags.contains(PodFlags::REQUEST_LAYOUT) {
                                        let mut layout_cx = LayoutCtx {
                                            cx_state: &mut cx_state,
                                            widget_state: root_state,
                                            brush_cx: graphics.font_system.clone(),
                                        };
                                        root_pod.layout(&mut layout_cx);
                                    }

                                    if root_pod
                                        .state
                                        .flags
                                        .contains(PodFlags::VIEW_CONTEXT_CHANGED)
                                    {
                                        let mut lifecycle_cx = LifeCycleCtx {
                                            cx_state: &mut cx_state,
                                            widget_state: root_state,
                                        };
                                        let view_context = ViewContext {
                                            window_origin: Point::origin(),
                                            mouse_pos: mouse_pos
                                                .clone()
                                                .map(|(x, y)| Point::new(x, y)),
                                        };
                                        root_pod.lifecycle(
                                            &mut lifecycle_cx,
                                            &LifeCycle::ViewContextChanged(view_context),
                                        );
                                    }

                                    if root_pod.state.flags.contains(PodFlags::REQUEST_PAINT) {
                                        let mut paint_cx = PaintCtx {
                                            cx_state: &mut cx_state,
                                            widget_state: root_state,
                                            brush_cx: graphics.font_system.clone(),
                                        };
                                        graphics.draw(|frame| {
                                            frame.clear([1.0; 4]);
                                            root_pod.paint(&mut paint_cx, frame);
                                        });
                                    }
                                    *view = Box::new(new_view);
                                } else {
                                    let mut cx = Cx { id_path: vec![] };
                                    let (new_id, new_state, element) = new_view.build(&mut cx);
                                    let mut new_root_pod = Pod::new(element);
                                    let mut layout_cx = LayoutCtx {
                                        cx_state: &mut cx_state,
                                        widget_state: root_state,
                                        brush_cx: graphics.font_system.clone(),
                                    };
                                    new_root_pod.layout(&mut layout_cx);

                                    let mut lifecycle_cx = LifeCycleCtx {
                                        cx_state: &mut cx_state,
                                        widget_state: root_state,
                                    };
                                    let view_context = ViewContext {
                                        window_origin: Point::origin(),
                                        mouse_pos: mouse_pos.clone().map(|(x, y)| Point::new(x, y)),
                                    };
                                    new_root_pod.lifecycle(
                                        &mut lifecycle_cx,
                                        &LifeCycle::ViewContextChanged(view_context),
                                    );
                                    let mut paint_cx = PaintCtx {
                                        cx_state: &mut cx_state,
                                        widget_state: root_state,
                                        brush_cx: graphics.font_system.clone(),
                                    };
                                    graphics.draw(|frame| {
                                        frame.clear([1.0; 4]);
                                        new_root_pod.paint(&mut paint_cx, frame);
                                    });
                                    *id = Some(new_id);
                                    *state = Some(new_state);
                                    *root_pod = Some(new_root_pod);
                                    *view = Box::new(new_view);
                                }
                            }
                            _ => {}
                        }
                    }
                    Event::AboutToWait => {
                        window.request_redraw();
                    }
                    _ => {}
                }
            })
            .expect("Run loop occurs with error!");
    }
}
